#region License

/* Copyright (c) 2008, Dusty Candland
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * * Neither the name of Dusty Candland nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data.SQLite;
using System.Security.Cryptography;

namespace NS3Library.SQLite
{
    internal class ObjectRepository : BaseRepository
    {
        private const string CREATE_TABLE =
            @"CREATE TABLE IF NOT EXISTS objects (
				id INTEGER PRIMARY KEY AUTOINCREMENT, bucket_id INTEGER, key TEXT, 
				last_modified TEXT, etag TEXT, size INTEGER, owner_id INTEGER, 
				storage_class INTEGER, content BLOB)";

        private const string INSERT =
            @"insert into objects (bucket_id, key, last_modified, etag, size, owner_id, storage_class, content)
						   values(@bucket_id, @key, @last_modified, @etag, @size, @owner_id, @storage_class, @content)";

        private const string DELETE_FOR_KEY =
            "delete from objects where bucket_id in (select id from buckets where name = @name) and key = @key";

        private const string SELECT_ID_FOR_KEY =
            "Select o.id from objects o inner join buckets b on b.id = o.bucket_id where b.name = @name and o.key = @key";

        private const string UPDATE =
            @"update objects set last_modified = @last_modified, etag = @etag, size = @size, 
						owner_id = @owner_id, storage_class = @storage_class, content = @content
						where bucket_id = @bucket_id and key = @key";

        private const string SELECT_FOR_KEY =
            "Select * from objects o inner join buckets b on b.id = o.bucket_id where b.name = @name and o.key = @key";

        private static readonly MD5 md5 = MD5.Create();

        public ObjectRepository(string connectionString)
            : base(connectionString)
        {
        }

        public void EnsureObjectsTable()
        {
            ExecuteNonQuery(CREATE_TABLE);
        }

        public void Insert(int bucketId, IS3Object s3Object)
        {
            ExecuteNonQuery(INSERT,
                            new SQLiteParameter("@bucket_id", bucketId),
                            new SQLiteParameter("@key", s3Object.Key),
                            new SQLiteParameter("@last_modified", DateTime.Now.ToString("o")),
                            new SQLiteParameter("@etag", CreateETag(s3Object.Content)),
                            new SQLiteParameter("@size", s3Object.Content.Length),
                            new SQLiteParameter("@owner_id", 0),
                            new SQLiteParameter("@storage_class", s3Object.StorageClass),
                            new SQLiteParameter("@content", s3Object.Content));
        }

        private static string CreateETag(byte[] content)
        {
            return Convert.ToBase64String(md5.ComputeHash(content));
        }

        public void Update(int bucketId, IS3Object s3Object)
        {
            ExecuteNonQuery(UPDATE,
                            new SQLiteParameter("@bucket_id", bucketId),
                            new SQLiteParameter("@key", s3Object.Key),
                            new SQLiteParameter("@last_modified", DateTime.Now.ToString("o")),
                            new SQLiteParameter("@etag", CreateETag(s3Object.Content)),
                            new SQLiteParameter("@size", s3Object.Content.Length),
                            new SQLiteParameter("@owner_id", 0),
                            new SQLiteParameter("@storage_class", s3Object.StorageClass),
                            new SQLiteParameter("@content", s3Object.Content));
        }

        public SQLiteS3Object Find(string bucketName, string key, NameValueCollection metadata)
        {
            ParseReadRowDelegate<SQLiteS3Object> parser = delegate(SQLiteDataReader reader)
                                                              {
                                                                  SQLiteS3ObjectKey k =
                                                                      SQLiteS3Parser.ParseObjectKeyDelegate(reader);
                                                                  var content = (byte[]) reader["content"];
                                                                  return new SQLiteS3Object(k.Id, bucketName, k.Key,
                                                                                            k.LastModified, k.ETag,
                                                                                            k.Size, k.Owner,
                                                                                            k.StorageClass, content,
                                                                                            metadata);
                                                              };
            var objects = new List<SQLiteS3Object>(
                ExecuteReader(SELECT_FOR_KEY, parser,
                              new SQLiteParameter("@name", bucketName),
                              new SQLiteParameter("@key", key)));

            if (objects.Count <= 0)
                return null;

            return objects[0];
        }

        public void Delete(string bucketName, string key)
        {
            ExecuteNonQuery(DELETE_FOR_KEY,
                            new SQLiteParameter("@name", bucketName),
                            new SQLiteParameter("@key", key));
        }

        public int GetObjectId(string bucketName, string key)
        {
            var ids = new List<int>(ExecuteReader(SELECT_ID_FOR_KEY, SQLiteS3Parser.IdParserDelegate,
                                                  new SQLiteParameter("@name", bucketName),
                                                  new SQLiteParameter("@key", key)));
            if (ids.Count > 0)
                return ids[0];
            return 0;
        }
    }
}